from __future__ import with_statement

import os
import fcntl
from threading import Lock
from pyfileutils.File import File
from contextlib import contextmanager

class LockFile(File):

    """Adds locking capability to a file.
    
    This locking is for all processes, including this one.  This is
    achieved by using syscalls for other processes, and a Lock for
    this process and its threads.
    
    """

    def __init__(self, filename=None, dir=None, overwrite=False, binary=False):
        """Initializes LockFile."""
        self._lock = Lock()
        File.__init__(self, filename, dir, overwrite, binary)

    def close(self):
        with self._lock:
            File.close(self)

    def seek(self, pos, mode=0):
        with self._lock:
            File.seek(self, pos, mode)

    @contextmanager
    def get_file(self, mode, seek=True, locked=True):
        """Opens a file object on self.filename.

        mode:   just like open(<filename>, <mode>)
        seek:   an optional boolean, synchronizes the file's position
                with the position of this File.  True by default.
        locked: a boolean which locks the file if given.
        
        """
        with self._lock:
            with self._get_file(mode, seek) as _file:
                if locked:
                    if mode.startswith('r'):
                        lock_type = fcntl.LOCK_SH
                    else:
                        lock_type = fcntl.LOCK_EX
                    fcntl.flock(_file.fileno(), lock_type)
                yield _file
                if locked:
                    fcntl.flock(_file.fileno(), fcntl.LOCK_UN)
            self._file.close()
            self._file = None

    def _read(self, size=-1, seek=True, update_pos=True):
        """Blah blah blah, private-ish method."""
        if not update_pos and seek:
            es = "Updating position without seeking would un-sync this File"
            raise ValueError(es)
        use_lock = (size and size > 0) or update_pos
        with self.get_file('r', seek=seek, locked=use_lock) as fobj:
            data = fobj.read(size)
            if seek and update_pos:
                self.pos += len(data)
            return data

